package org.lora.mvc.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.lora.Constant;
import org.lora.core.common.CommonCoreServiceImpl;
import org.lora.core.context.WebContext;
import org.lora.exception.MVCException;
import org.lora.mvc.IMvcService;
import org.lora.mvc.annotation.FileDownload;
import org.lora.mvc.annotation.RequestPath;
import org.lora.mvc.annotation.ResponseJSON;
import org.lora.mvc.annotation.RestfulService;
import org.lora.mvc.entity.ActionMethodEntity;
import org.lora.mvc.entity.ViewData;
import org.lora.mvc.security.ISecurityService;
import org.lora.util.StringUtil;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.IOUtils;

/**
 * <p>
 * Title:MvcServiceImpl
 * </p>
 * <p>
 * Desription:MVC处理Service实现
 * </p>
 * Create Time:2016年7月5日 下午7:16:52
 * 
 * @author Bladnir@outlook.com
 *         <p>
 *         History:
 *         </p>
 *         2016年7月5日 Bladnir Create</br>
 */
public class MvcServiceImpl extends CommonCoreServiceImpl implements IMvcService {

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.lora.mvc.IMvcService#getActionMap(java.util.List)
	 */
	@Override
	public Map<String, ActionMethodEntity> getActionMap(List<Object> actionObjectList) throws NoSuchMethodException {

		Map<String, ActionMethodEntity> methodMap = new HashMap<>();

		for (Object actionObject : actionObjectList) {

			// 真实的action的class
			Class<?> actionClass = actionObject.getClass().getSuperclass();
			// cglib 生成的代理class
			Class<?> proxyClass = actionObject.getClass();

			Method[] methodArray = actionClass.getMethods();
			RequestPath actionRequestPathAnnotaion = actionClass.getAnnotation(RequestPath.class);
			String actionReauestPath = null;
			if (actionRequestPathAnnotaion != null) {
				actionReauestPath = actionRequestPathAnnotaion.path();
			}
			for (Method method : methodArray) {
				RequestPath requestPathAnnotaion = method.getAnnotation(RequestPath.class);
				if (requestPathAnnotaion == null) {// 如果没有请求路径 这个方法就被忽略
					continue;
				}

				// 代理类的method对象
				Method proxyMethod = proxyClass.getMethod(method.getName(), method.getParameterTypes());

				ActionMethodEntity ame = new ActionMethodEntity();
				ame.setActionObject(actionObject);
				ame.setMethod(proxyMethod);
				ame.setOriginMethod(method);

				String requestPath = StringUtil.BLANK;

				if (StringUtil.isNotNull(actionReauestPath)) {
					requestPath = requestPath + actionReauestPath;
				}
				requestPath = requestPath + requestPathAnnotaion.path();

				ame.setRequestPath(requestPath);

				methodMap.put(requestPath, ame);
			}
		}

		return methodMap;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.lora.mvc.IMvcService#excuteRequest(javax.servlet.http.HttpServletRequest
	 * , javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void excuteRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String path = request.getContextPath();
		String requestURI = request.getRequestURI();
		String requestString = requestURI.replace(path, StringUtil.BLANK).replace(".do", StringUtil.BLANK);

		if (StringUtil.isNull(requestString)) {
			return;
		}

		Map<String, ActionMethodEntity> actionMap = WebContext.getInstance().getActionMap();

		if (!actionMap.containsKey(requestString)) {
			throw new MVCException("Err006", "cant find action method by request=[" + requestString + "]");
		}

		ActionMethodEntity ame = actionMap.get(requestString);

		Method originMethod = ame.getOriginMethod();

		if (originMethod.getAnnotation(ResponseJSON.class) != null) {// 写回json字符串到页面
			jsonHanle(ame, request, response);
		} else if (originMethod.getAnnotation(RestfulService.class) != null) {// Rest服务
			RestfulService restfulServiceAnnotation = originMethod.getAnnotation(RestfulService.class);
			restfulServiceHandle(ame, request, response, restfulServiceAnnotation);
		} else if (originMethod.getAnnotation(FileDownload.class) != null) {// 文件下载
			fileDownloadHandle(ame, request, response);
		} else {// 没有特殊的注解的时候 则认为是跳转类的请求
			viewDataHanle(ame, request, response);
		}

	}

	/**
	 * 下载文件处理
	 * 
	 * @param ame
	 * @param request
	 * @param response
	 * @param filedownAnnotation
	 * @throws MVCException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws IOException
	 */
	private void fileDownloadHandle(ActionMethodEntity ame, HttpServletRequest request, HttpServletResponse response) throws MVCException,
			IllegalAccessException, InvocationTargetException, IOException {

		List<Object> parameterList = new ArrayList<>();
		parameterList.add(request);
		parameterList.add(response);

		Object returnObj = this.excuteMethod(ame, parameterList);

		if (returnObj == null) {
			// 如果返回值为null 什么都不做 但是也不会报错
			return;
		}

		if (!(returnObj instanceof File)) {
			throw new MVCException("MVCErr05", ame.getMethod().getName() + "return value is[" + returnObj + "].it should be java.io.File");
		}

		// 得到文件对象
		File downloadFile = (File) returnObj;

		// 判断文件是否存在
		if (!downloadFile.exists()) {
			throw new MVCException("MVCErr01", "cant find file by fileName=[" + downloadFile + "]");
		}

		// 判断文件是不是一个文件（也有可能是个文件夹）
		if (!downloadFile.isFile()) {
			throw new MVCException("MVCErr06", "fileName=[" + downloadFile + "] is not a File");
		}

		// 下载文件
		downloadFile(response, downloadFile);

	}

	/**
	 * 下载文件
	 * 
	 * @param response
	 * @param downloadFile
	 */
	private void downloadFile(HttpServletResponse response, File downloadFile) throws IOException {

		try (InputStream inputStream = new FileInputStream(downloadFile); ServletOutputStream ouputStream = response.getOutputStream()) {

			// 设置相应类型application/octet-stream（文件下载类型）
			response.setContentType(Constant.FILE_CONTENT_TYPE);
			// 设置头信息（以附件形式，打开下载文件）
			response.setHeader("Content-Disposition", "attachment;filename=\"" + downloadFile.getName() + "\"");
			// 循环把输入流写入输出流
			byte[] b = new byte[1024];
			int n;
			while ((n = inputStream.read(b)) != -1) {
				ouputStream.write(b, 0, n);
			}
		}

	}

	/**
	 * 处理Restful服务请求
	 * 
	 * @param ame
	 * @param request
	 * @param response
	 * @param restfulServiceAnnotation
	 * @throws Exception
	 */
	private void restfulServiceHandle(ActionMethodEntity ame, HttpServletRequest request, HttpServletResponse response,
			RestfulService restfulServiceAnnotation) throws Exception {

		List<Object> parameterList = new ArrayList<>();
		parameterList.add(request);
		parameterList.add(response);

		// 获取body体的数据 并转化为字符串
		InputStream is;
		String contentString;
		is = request.getInputStream();
		contentString = IOUtils.toString(is);
		contentString = new String(contentString.getBytes(Charset.defaultCharset()), Charset.forName("UTF-8"));

		// 根据注解上的参数类型 转化字符串为对应的对象 传给Action中的方法
		if (restfulServiceAnnotation.layout().equals(RestfulService.Layout.JSON)) {// 返回的是json字符串

			JSONObject json = null;

			if (StringUtil.isNotNull(contentString)) {
				json = JSON.parseObject(contentString);
			}

			parameterList.add(json);

		} else if (restfulServiceAnnotation.layout().equals(RestfulService.Layout.XML)) {// 返回的是xml字符串
			StringReader sr = new StringReader(contentString);
			InputSource inputSource = new InputSource(sr);
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(inputSource);

			parameterList.add(document);

		}

		// 执行代理类的方法
		Object returnObj = excuteMethod(ame, parameterList);

		// 写回json数据
		returnJsonResult(response, returnObj);

	}

	/**
	 * 处理跳转类的请求
	 * 
	 * @param ame
	 * @param request
	 * @param response
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws MVCException
	 * @throws IOException
	 * @throws ServletException
	 */
	private void viewDataHanle(ActionMethodEntity ame, HttpServletRequest request, HttpServletResponse response)
			throws IllegalAccessException, InvocationTargetException, MVCException, IOException, ServletException {

		List<Object> parameterList = new ArrayList<>();
		parameterList.add(request);
		parameterList.add(response);

		// 执行方法
		Object returnObj = excuteMethod(ame, parameterList);

		if (returnObj == null) {
			throw new MVCException("Err002", "action method return null");
		}

		if (!(returnObj instanceof ViewData)) {
			throw new MVCException("Err003", "action method return type is[" + returnObj.getClass() + "] it should be " + ViewData.class);
		}
		ViewData viewData = (ViewData) returnObj;

		if (StringUtil.isNull(viewData.getDirection())) {
			throw new MVCException("Err004", "action method return viewData.getDirection is null");
		}

		if (viewData.isSendRedirect()) {
			// 处理重定向
			requestRedirect(request, response, viewData);
		} else {
			// 处理Dispatcher
			requestDispatcher(request, response, viewData);
		}
	}

	/**
	 * 处理重定向
	 * 
	 * @param request
	 * @param response
	 * @param viewData
	 * @throws IOException
	 */
	private void requestRedirect(HttpServletRequest request, HttpServletResponse response, ViewData viewData) throws IOException {

		String redirectUrl = viewData.getDirection();

		Map<String, Object> dataMap = viewData.getDataMap();

		if (dataMap != null) {

			StringBuilder sb = new StringBuilder();

			for (Entry<String, Object> entry : dataMap.entrySet()) {
				request.setAttribute(entry.getKey(), entry.getValue());
				sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
			}

			redirectUrl = redirectUrl + sb.toString();
		}

		response.sendRedirect(redirectUrl);

	}

	/**
	 * 处理Dispatcher
	 * 
	 * @param request
	 * @param response
	 * @param viewData
	 * @throws ServletException
	 * @throws IOException
	 */
	private void requestDispatcher(HttpServletRequest request, HttpServletResponse response, ViewData viewData) throws ServletException,
			IOException {

		Map<String, Object> dataMap = viewData.getDataMap();

		if (dataMap != null) {

			Set<Entry<String, Object>> dataMapSet = dataMap.entrySet();

			for (Entry<String, Object> entry : dataMapSet) {
				request.setAttribute(entry.getKey(), entry.getValue());
			}
		}

		RequestDispatcher dispatcher = request.getRequestDispatcher(viewData.getDirection());
		dispatcher.forward(request, response);

	}

	/**
	 * 执行方法
	 * 
	 * @param ame
	 * @param parameterList
	 * @return
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private Object excuteMethod(ActionMethodEntity ame, List<Object> parameterList) throws IllegalAccessException,
			InvocationTargetException {

		Method method = ame.getMethod();

		List<Object> realParameterList = new ArrayList<>();

		Class<?>[] typeArray = method.getParameterTypes();

		for (int i = 0; i < typeArray.length; i++) {
			Class<?> clz = typeArray[i];
			// 检查类型是否匹配
			realParameterList.add(getParameterObject(parameterList, clz));
		}

		method.setAccessible(true);
		// 此处只能接受一个数组，别的是不行的
		return method.invoke(ame.getActionObject(), realParameterList.toArray());
	}

	/**
	 * 从参数List中筛选出 和clz类型对应的参数<br>
	 * 如果筛选不出来 则返回null<br>
	 * 
	 * @param parameterList
	 * @param clz
	 * @return
	 */
	private Object getParameterObject(List<Object> parameterList, Class<?> clz) {

		for (Object obj : parameterList) {
			if (clz.isAssignableFrom(obj.getClass())) {
				return obj;
			}
		}

		return null;
	}

	/**
	 * 处理返回json的 ajax请求
	 * 
	 * @param ame
	 * @param request
	 * @param response
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws IOException
	 */
	private void jsonHanle(ActionMethodEntity ame, HttpServletRequest request, HttpServletResponse response) throws IllegalAccessException,
			InvocationTargetException, IOException {

		List<Object> parameterList = new ArrayList<>();
		parameterList.add(request);
		parameterList.add(response);

		// 执行代理类的方法
		Object returnObj = excuteMethod(ame, parameterList);

		// 写回JSON数据
		returnJsonResult(response, returnObj);

	}

	/**
	 * 写回json数据
	 * 
	 * @param response
	 * @param obj
	 * @throws IOException
	 */
	private void returnJsonResult(HttpServletResponse response, Object obj) throws IOException {
		response.setContentType(Constant.UTF8_CONTENT_TYPE);

		PrintWriter out = null;
		try {
			out = response.getWriter();
			JSON.writeJSONStringTo(obj, out, new SerializerFeature[] {});
			if (out != null) {
				out.flush();
			}
		} catch (IOException e) {
			systemLog.error(e);
			throw e;
		} finally {
			if (out != null) {
				out.flush();
				out.close();
				out = null;
			}
		}
	}

}
